﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;

namespace TotalApplication.AngelSix
{
    /// <summary>
    /// A base class to run animation method when a boolean is set to true
    /// and a reverse animation when set to false
    /// </summary>
    /// <typeparam name="Parent"></typeparam>
    public abstract class AnimationBaseProperty<Parent> : BaseAttachedProperty<Parent, bool>
        where Parent : BaseAttachedProperty<Parent, bool>, new()
    {
        #region Protected Properties

        /// <summary>
        /// True if this is the very first time the value has been updated
        /// Used to make sure we run the logic at least once during first load
        /// </summary>
        protected Dictionary<WeakReference, bool> mAlreadyLoaded = new Dictionary<WeakReference, bool>();

        /// <summary>
        /// The most recent value used if we get a value changed before we do the first load
        /// </summary>
        protected Dictionary<WeakReference, bool> mFirstLoadValue = new Dictionary<WeakReference, bool>();

        #endregion

        public override void OnValueUpdated(DependencyObject sender, object value)
        {
            // Get element
            if (!(sender is FrameworkElement element))
                return;

            // Try and get the already loaded reference
            var alreadyLoadedReference = mAlreadyLoaded.FirstOrDefault(f => f.Key.Target == element);

            // Try and get the first load reference
            var firstLoadReference = mFirstLoadValue.FirstOrDefault(f => f.Key.Target == element);

            //// If same value , don't fire
            //if (sender.GetValue(ValueProperty) == value && alreadyLoadedReference.Key != null)
            //    return;

            // Don't fire if the value doesn't change
            if ((bool)sender.GetValue(ValueProperty) == (bool)value && alreadyLoadedReference.Key != null)
                return;

            // On first load
            if (alreadyLoadedReference.Key == null)
            {
                // Create weak reference
                var weakReference = new WeakReference(element);

                // Flag that we are in first load but have not finished it yet
                mAlreadyLoaded[weakReference] = false;

                // Start off hidden the element before we decide how to animate it in
                element.Visibility = Visibility.Hidden;

                // Create a single self-unhookable event for the elements loaded event
                RoutedEventHandler? onLoaded = null;
                onLoaded = async (ss, ee) =>
                {
                    // Unhook ourselves
                    element.Loaded -= onLoaded;

                    // Slight delay to ensure everything is ready
                    await Task.Delay(5);

                    // Refresh the first load value in case it changed since the 5ms delay
                    firstLoadReference = mFirstLoadValue.FirstOrDefault(f => f.Key.Target == sender);

                    // Do desired animation
                    DoAnimation(element, firstLoadReference.Key != null ? firstLoadReference.Value : (bool)value, true);

                    // Flag that we have finished first load
                    mAlreadyLoaded[weakReference] = true;
                };

                // Hook into the loaded event of element
                element.Loaded += onLoaded;
            }
            // If we have started a first load but not find the animation yet , update the property
            else if (alreadyLoadedReference.Value == false)
            {
                // 确保动画执行的最终一致性，因为有可能控件加载之前多次更改了值，所以一定要保证执行动画前的值是最新的
                mFirstLoadValue[new WeakReference(sender)] = (bool)value;
            }
            else
            {
                DoAnimation(element, (bool)value, false);
            }
        }

        /// <summary>
        /// The animation method tha is fired when the value changes
        /// </summary>
        /// <param name="element"></param>
        /// <param name="value"></param>
        protected virtual void DoAnimation(FrameworkElement element, bool value, bool firstLoad) { }
    }

    /// <summary>
    /// Image On Load Animation
    /// </summary>
    public class FadeInImageOnLoadProperty : AnimationBaseProperty<FadeInImageOnLoadProperty>
    {
        public override void OnValueUpdated(DependencyObject sender, object value)
        {
            // Make sure we have an image
            if (!(sender is Image image)) return;

            // If we want to animate in...        
            if ((bool)value)
                image.TargetUpdated += Image_TargetUpdated;
            else
                image.TargetUpdated -= Image_TargetUpdated;

        }

        private async void Image_TargetUpdated(object? sender, System.Windows.Data.DataTransferEventArgs e)
        {
            if (sender == null) return;

            await ((Image)sender).FadeInAsync(false);
        }
    }


    /// <summary>
    /// Left滑入动画
    /// </summary>
    public class AnimateSlideInFromLeftProperty : AnimationBaseProperty<AnimateSlideInFromLeftProperty>
    {
        protected override async void DoAnimation(FrameworkElement element, bool value, bool firstLoad)
        {
            // 通过对状态的判断，决定动画加载的时间， 所以实际上这个初次加载是极其有作用的
            // 当然按这么说，完全可以加更多附加属性的隐藏属性
            if (value)
            {
                // Animate in
                await element.SlideAndFadeInFromLeftAsync(firstLoad ? 0 : 0.3f, false);
            }
            else
            {
                // Animate out
                await element.SlideAndFadeOutToLeftAsync(firstLoad ? 0 : 0.3f, false);
            }
        }
    }

    /// <summary>
    /// Right滑入动画
    /// </summary>
    public class AnimateSlideInFromRightProperty : AnimationBaseProperty<AnimateSlideInFromRightProperty>
    {
        protected override async void DoAnimation(FrameworkElement element, bool value, bool firstLoad)
        {
            if (value)
            {
                // Animate in
                await element.SlideAndFadeInFromRightAsync(firstLoad ? 0 : 0.3f, true);
            }
            else
            {
                // Animate out
                await element.SlideAndFadeOutToRightAsync(firstLoad ? 0 : 0.3f, true);
            }
        }
    }

    /// <summary>
    /// Bottom滑入动画
    /// </summary>
    public class AnimateSlideInFromBottomProperty : AnimationBaseProperty<AnimateSlideInFromBottomProperty>
    {
        protected override async void DoAnimation(FrameworkElement element, bool value, bool firstLoad)
        {
            // 通过对状态的判断，决定动画加载的时间， 所以实际上这个初次加载是极其有作用的
            // 当然按这么说，完全可以加更多附加属性的隐藏属性
            if (value)
            {
                // Animate in
                await element.SlideAndFadeInFromBottomAsync(firstLoad ? 0 : 0.3f, true);
            }
            else
            {
                // Animate out
                await element.SlideAndFadeOutToBottomAsync(firstLoad ? 0 : 0.3f, true);
            }
        }
    }

    /// <summary>
    /// Bottom滑入动画【仅限第一次添加该项目时】
    /// </summary>
    public class AnimateSlideInFromBottomOnLoadProperty : AnimationBaseProperty<AnimateSlideInFromBottomOnLoadProperty>
    {
        protected override async void DoAnimation(FrameworkElement element, bool value, bool firstLoad)
        {
            // Animate in
            // 仅在第一次执行添加逻辑的时候设置为true执行，同时keepMargin设置为false，以实现动画效果
            await element.SlideAndFadeInFromBottomAsync(!value ? 0 : 0.3f, !value);
        }
    }

    /// <summary>
    /// Fade In/Out Animation
    /// </summary>
    public class AnimateFadeInProperty : AnimationBaseProperty<AnimateFadeInProperty>
    {
        protected override async void DoAnimation(FrameworkElement element, bool value, bool firstLoad)
        {
            if (value)
            {
                // Animate in
                await element.FadeInAsync(firstLoad, firstLoad ? 0 : 0.3f);
            }
            else
            {
                // Animate out
                await element.FadeOutAsync(firstLoad, firstLoad ? 0 : 0.3f);
            }
        }
    }
}
